package org.ix.dynamicapis.configure;

import org.ix.dynamicapis.configure.exception.*;
import org.ix.dynamicapis.configure.models.*;
import org.ix.dynamicapis.configure.utils.*;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.mapping.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.OutputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
public abstract class BaseApiTemplate {

    /**
     * 获取API配置接口地址
     */
    protected String getApisUrl;
    /**
     * 接口所属系统
     */
    protected String system;

    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 本地mybatis相关配置缓存，key是接口全路径
     */
    protected Map<String, ApiSqlModel> mybatisSqlMap;

    protected final Object refreshLock = new Object();
    /**
     * 用于获取参数
     */
    protected final RestTemplate restTemplate = getRestTemplate();

    /**
     * @param getApisUrl 获取接口配置的url
     * @param system     系统名称
     */
    public BaseApiTemplate(String getApisUrl, String system, RedisTemplate<String, Object> redisTemplate) {
        this.getApisUrl = getApisUrl;
        this.system = system;
        this.redisTemplate = redisTemplate;
        this.mybatisSqlMap = new HashMap<>();
    }

    /**
     * 执行不带参数的接口
     *
     * @param topic
     * @param version
     * @param path
     * @return
     * @throws Exception
     */
    public Object exec(String version, String topic, String path) throws Exception {

        return exec(version, topic, path, new HashMap<>());
    }

    /**
     * 执行带参数的接口
     *
     * @param topic
     * @param version
     * @param path
     * @param params
     * @return
     * @throws Exception
     */
    public Object exec(String version, String topic, String path, Map<String, Object> params) throws Exception {

        String fullPath = ApiTemplateUtils.getFullPath(version, topic, path);
        if (StringUtils.isBlank(fullPath)) return null;

        ApiSqlModel apiSqlModel = getApiSqlModel(fullPath);

        long timestamp = new Date().getTime();
        log.info("正在获取数据，system:{}, fullPath:{}，timestamp:{}", system, fullPath, timestamp);
        // 从缓存获取数据
        Object result;
        if (apiSqlModel.isCacheable()) {
            log.info("从redis获取数据，timestamp:{}", timestamp);
            result = getResultCache(fullPath, apiSqlModel.getCacheTime(), timestamp);
            if (result != null) {
                log.info("缓存数据不为空，直接返回，timestamp:{}", timestamp);
                return result;
            }
        }

        final Map<String, Object> paramMap = params == null ? new HashMap<>() : params;
        // 设置参数实际类型和默认值
        setParams(paramMap, apiSqlModel);

        // 获取要执行的sql和参数
        BoundSql boundSql = apiSqlModel.getMs().getBoundSql(paramMap);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        Map<String, Object> objectMap = (Map<String, Object>) boundSql.getParameterObject();
        String resultSql = boundSql.getSql().replaceAll("\\s+", " ");
        List<Object> finalParams = new ArrayList<>();
        for (ParameterMapping mapping : parameterMappings) {
            Object additionalParameter;
            if ((additionalParameter = boundSql.getAdditionalParameter(mapping.getProperty())) == null) {
                finalParams.add(objectMap.get(mapping.getProperty()));
            } else {
                finalParams.add(additionalParameter);
            }
        }
        log.info("从数据库获取数据，timestamp:{}，resultSql:{}, params:{}", timestamp, resultSql, JsonUtils.toJsonString(finalParams));

        ConfigModel config = getConfigByPath(fullPath);
        List<Map<String, Object>> apiResult = config.getJdbcTemplate().queryForList(resultSql, finalParams.toArray());
        if (apiResult == null) return null;

        // 返回结果不为空才可以缓存
        boolean resultCacheable = !apiResult.isEmpty();
        // 如果返回结果是单个对象
        if (apiSqlModel.getResultType() == 2) {
            if (apiResult.size() > 1) {
                log.error("返回结果不唯一，timestamp:{}, params:{}", timestamp, JsonUtils.toJsonString(params));
                throw new ResultNotUniqueException("返回结果不唯一");
            }
            result = resultCacheable ? apiResult.get(0) : null;
        } else {
            result = apiResult;
        }

        // 添加缓存
        if (apiSqlModel.isCacheable() && resultCacheable) {
            log.info("设置缓存，timestamp:{}", timestamp);
            setResultCache(fullPath, apiSqlModel.getCacheTime(), result, timestamp);
        }
        return result;
    }

    /**
     * 导出数据
     *
     * @param path
     * @param param
     * @param sheetName
     * @param condition
     * @param outputStream
     * @throws Exception
     */
    public void export(String version, String topic, String path, Map<String, Object> param, String sheetName, String condition, OutputStream outputStream) throws Exception {

        String fullPath = ApiTemplateUtils.getFullPath(version, topic, path);
        Object result = exec(version, topic, path, param);
        if (!(result instanceof List)) {
            log.error("导出内容必须是列表，path:" + fullPath + ",params:" + JsonUtils.toJsonString(param));
            throw new Exception("导出内容必须是列表");
        }

        ApiSqlModel apiSqlModel = getApiSqlModel(fullPath);
        ExportToExcelUtils.exportExcel(sheetName, apiSqlModel.getApiModel(), (List<Map<String, Object>>) result, condition, outputStream);
    }

    /**
     * 刷新本地接口缓存
     *
     * @param version
     * @param topic
     * @param path
     * @return
     * @throws Exception
     */
    public boolean refresh(String dataSource, String version, String topic, String path) throws Exception {
        List<ApiModel> apis = getApisResult(dataSource, version, topic, path);
        String fullPath = ApiTemplateUtils.getFullPath(version, topic, path);
        synchronized (refreshLock) {
            log.info("正在删除缓存..., datasource: {}, path: {}", dataSource, fullPath);
            delCache(dataSource, fullPath);
            log.info("正在设置缓存..., datasource: {}, path: {}", dataSource, fullPath);
            setCache(dataSource, apis);
        }
        return true;
    }

    private RestTemplate getRestTemplate() {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(60000);
        requestFactory.setReadTimeout(60000);
        return new RestTemplate(requestFactory);
    }

    /**
     * 根据系统和路径获取相关接口配置信息
     *
     * @param datasource
     * @param topic
     * @param version
     * @param path
     * @return
     */
    private List<ApiModel> getApisResult(String datasource, String version, String topic, String path) {
        StringBuilder urlBuilder = new StringBuilder();
        urlBuilder.append((getApisUrl.contains("?") ? "&" : "?"));
        urlBuilder.append("system=");
        urlBuilder.append(system);
        urlBuilder.append("&datasource=");
        urlBuilder.append(datasource);
        if (StringUtils.isNotBlank(topic) && StringUtils.isNotBlank(version) && StringUtils.isNotBlank(path)) {
            urlBuilder.append("&path=");
            urlBuilder.append(path);
            urlBuilder.append("&topic=");
            urlBuilder.append(topic);
            urlBuilder.append("&version=");
            urlBuilder.append(version);
        }
        String result = restTemplate.getForObject(getApisUrl + urlBuilder, String.class);
        return JsonUtils.parseList(JsonUtils.toJsonString(result), ApiModel.class);
    }

    /**
     * 获取ApiSqlModel
     *
     * @param fullPath
     * @return
     * @throws Exception
     */
    private ApiSqlModel getApiSqlModel(String fullPath) throws Exception {

        // 从本地缓存中获取
        log.info("正在从本地缓存获取接口配置...");
        ApiSqlModel apiSqlModel = mybatisSqlMap.getOrDefault(fullPath, null);
        // 比较版本是否一致
        if (apiSqlModel == null) {
            throw new ApiNotFoundException("接口未配置");
        }

        return apiSqlModel;
    }

    /**
     * 设置参数实际类型和默认类型
     *
     * @param params
     * @param apiSqlModel
     */
    private void setParams(final Map<String, Object> params, ApiSqlModel apiSqlModel) {

        // 设置默认参数
        Map<String, Object> defaultValues = apiSqlModel.getDefaultValues();
        defaultValues.forEach((key, value) -> {
            if (!params.containsKey(key)) {
                params.put(key, value);
            }
        });
        if (params.isEmpty()) return;

        List<ApiParamModel> apiParams = apiSqlModel.getApiModel().getApiParams();
        if (apiParams == null) return;

        apiParams.stream().filter(apiParam -> StringUtils.isNotBlank(apiParam.getType())).forEach(apiParam -> {
            Object object = params.getOrDefault(apiParam.getName(), null);
            if (object == null) return;
            Object val = TypeParseUtils.getRealValue(apiParam.getType(), object);
            if (val != null) params.put(apiParam.getName(), val);
        });
    }

    /**
     * 把结果缓存到redis
     *
     * @param fullPath
     * @param cacheTime
     * @param result
     */
    private void setResultCache(String fullPath, Integer cacheTime, Object result, long timestamp) {
        String key = getResultRedisKey(fullPath, cacheTime);
        if (cacheTime > 1440) cacheTime = 1440;
        else if (cacheTime > 60) cacheTime = 60;
        try {
            redisTemplate.opsForValue().set(key, result, cacheTime, TimeUnit.MINUTES);
        } catch (Exception e) {
            log.error("设置缓存失败，timestamp:" + timestamp, e);
        }
    }

    /**
     * 从redis获取缓存结果
     *
     * @param fullPath
     * @param cacheTime
     * @return
     */
    private Object getResultCache(String fullPath, Integer cacheTime, long timestamp) {
        String key = getResultRedisKey(fullPath, cacheTime);
        try {
            return redisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            log.error("获取缓存缓存数据异常，timestamp:" + timestamp, e);
        }
        return null;
    }

    /**
     * 获取接口结果的key
     *
     * @param fullPath
     * @param cacheTime
     * @return
     */
    private String getResultRedisKey(String fullPath, Integer cacheTime) {
        LocalDateTime localDate = LocalDateTime.now();
        String time = "";
        if (cacheTime / 1440 > 0) time = ":" + localDate.getDayOfMonth();
        else if (cacheTime / 60 > 0) time = ":" + localDate.getHour();
        return String.format("%s:api:%s%s", system, fullPath, time);
    }

    /**
     * 删除缓存
     *
     * @param dataSource
     * @param fullPath
     */
    abstract void delCache(String dataSource, String fullPath);

    /**
     * 设置缓存
     *
     * @param dataSource
     * @param apis
     * @throws ParserConfigurationException
     * @throws SAXException
     * @throws IOException
     */
    abstract void setCache(String dataSource, List<ApiModel> apis) throws ParserConfigurationException, SAXException, IOException;

    /**
     * 根据路径获取配置
     *
     * @param fullPath
     * @return
     */
    abstract ConfigModel getConfigByPath(String fullPath);
}
